package com.wisecoach.gatewayplus.transaction.lockedtx;

import com.wisecoach.gatewayplus.bind.ContractArgs;
import com.wisecoach.gatewayplus.bind.ContractCommand;
import com.wisecoach.gatewayplus.transaction.ChaincodeException;
import com.wisecoach.gatewayplus.transaction.ContractExecutor;
import com.wisecoach.gatewayplus.utils.TransactionUtils;
import com.wisecoach.util.CollectionUtils;
import org.hyperledger.fabric.client.*;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/**
 * {@code @author:} wisecoach
 * {@code @date:} 2023/9/26 上午10:01
 * {@code @version:} 1.0.0
 */


public class LockedTxContractExecutor implements ContractExecutor {

    private final LockedTxResource resource;
    // 上锁重试最大次数
    private final int maxRetry;
    // 上锁重试间隔
    private final int retryInterval;

    public LockedTxContractExecutor(LockedTxResource resource, int maxRetry, int retryInterval) {
        this.resource = resource;
        this.maxRetry = maxRetry;
        this.retryInterval = retryInterval;
    }

    /**
     * evaluate只需要正常地调用evaluate即可
     * @param contract 要执行的链码对象
     * @param command 要执行的链码信息
     * @param args 链码交易参数
     */
    @Override
    public byte[] evaluate(Contract contract, ContractCommand command, ContractArgs args) {
        try {
            Proposal.Builder builder = contract.newProposal(command.getTransactionName());
            if (!CollectionUtils.isEmpty(args.getTransientMap())) {
                builder.putAllTransient(args.getTransientMap());
            }
            Proposal proposal = builder.addArguments(args.getArgs()).build();
            return proposal.evaluate();
        } catch (GatewayException e) {
            throw new ChaincodeException("链码执行失败", e);
        }
    }


    /**
     * 首先先进行背书，然后解析其读写集，对对应的key进行上锁，上锁失败就尝试几次后，多次失败后报错；
     * 上锁成功后，将背书后的交易放入到待提交的交易池中，等待整个事务提交时一起提交
     * @param contract 要执行的链码对象
     * @param command 要执行的链码信息
     * @param args 链码交易参数
     */
    @Override
    public byte[] submit(Contract contract, ContractCommand command, ContractArgs args) {
        try {
            Proposal.Builder builder = contract.newProposal(command.getTransactionName());
            if (!CollectionUtils.isEmpty(args.getTransientMap())) {
                builder.putAllTransient(args.getTransientMap());
            }
            Proposal proposal = builder.addArguments(args.getArgs()).build();
            Transaction transaction = proposal.endorse();
            TxRWSet txRWSet = TransactionUtils.resolveRWSet(transaction);
            boolean success = resource.lock(txRWSet);
            for (int i = 0; i < maxRetry && !success; i++) {
                TimeUnit.MILLISECONDS.sleep(retryInterval);
                success = resource.lock(txRWSet);
            }
            if (!success) {
                throw new ChaincodeException(String.format("上锁失败，已尝试%d次", maxRetry));
            }
            resource.pushTx(new LockedTxContext(transaction, txRWSet));
            return transaction.getResult();
        } catch (EndorseException | InterruptedException e) {
            throw new ChaincodeException("链码执行失败", e);
        }
    }
}
